package com.zyx.javademo.stream;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;

/**
 * @author Yaxi.Zhang
 * @since 2022/7/8 18:19
 * desc: 测试groupby
 * reference: https://www.cnblogs.com/donleo123/p/15621094.html
 */
public class GroupByDemo {

    List<Employee> employees = new ArrayList<>();

    public static void main(String[] args) {
        GroupByDemo instance = new GroupByDemo();
        instance.init();
        // instance.groupingByCity();
        // instance.groupingByCount();
        // instance.groupingByAverage();
        // instance.groupingBySum();
        // instance.groupingByString();
        // instance.groupingByList();
        instance.groupingBySet();
        // instance.groupingByObject();
        // instance.groupingBys();
        // instance.groupingByDistinct();
    }

    /**
     * 数据初始化
     */
    public void init() {
        List<String> citys = Arrays.asList("湖南", "湖北", "四川", "广东 ");
        for (int i = 0; i < 10; i++) {
            Random random = new Random();
            int index = random.nextInt(4);
            Employee employee = new Employee(citys.get(index), "姓名" + i, (random.nextInt(4) * 10 - random.nextInt(4)),
                    (random.nextInt(4) * 1000 - random.nextInt(4)));
            employees.add(employee);
        }
    }

    /**
     * 使用java8 stream groupingBy操作,按城市分组list
     */
    public void groupingByCity() {
        Map<String, List<Employee>> map = employees.stream().collect(Collectors.groupingBy(Employee::getCity));

        map.forEach((k, v) -> System.out.println(k + " = " + v));
        //四川 = [GroupBy.Employee(city=四川, name=姓名1, age=30, amount=999), GroupBy.Employee(city=四川, name=姓名2, age=8, amount=2000), GroupBy.Employee(city=四川, name=姓名3, age=30, amount=2997)]
        //湖南 = [GroupBy.Employee(city=湖南, name=姓名7, age=30, amount=-3), GroupBy.Employee(city=湖南, name=姓名8, age=19, amount=1998)]
        //湖北 = [GroupBy.Employee(city=湖北, name=姓名4, age=29, amount=-2), GroupBy.Employee(city=湖北, name=姓名5, age=10, amount=1997)]
        //广东  = [GroupBy.Employee(city=广东 , name=姓名0, age=28, amount=1998), GroupBy.Employee(city=广东 , name=姓名6, age=29, amount=1998), GroupBy.Employee(city=广东 , name=姓名9, age=7, amount=1000)]
    }

    /**
     * 使用java8 stream groupingBy操作,按城市分组list统计count
     */
    public void groupingByCount() {
        Map<String, Long> map = employees.stream()
                .collect(Collectors.groupingBy(Employee::getCity, Collectors.counting()));

        map.forEach((k, v) -> System.out.println(k + " = " + v));
        //四川 = 1
        //湖北 = 3
        //湖南 = 4
        //广东  = 2
    }

    /**
     * 使用java8 stream groupingBy操作,按城市分组list并计算分组年龄平均值
     */
    public void groupingByAverage() {
        Map<String, Double> map = employees.stream()
                .collect(Collectors.groupingBy(Employee::getCity, Collectors.averagingInt(Employee::getAge)));

        map.forEach((k, v) -> System.out.println(k + " = " + v));
        //四川 = 15.0
        //湖北 = 21.25
        //湖南 = 18.333333333333332
        //广东  = 9.0
    }

    /**
     * 使用java8 stream groupingBy操作,按城市分组list并计算分组销售总值
     */
    public void groupingBySum() {
        Map<String, Long> map = employees.stream()
                .collect(Collectors.groupingBy(Employee::getCity, Collectors.summingLong(Employee::getAmount)));

        map.forEach((k, v) -> System.out.println(k + " = " + v));
        //四川 = 3995
        //湖北 = 2995
        //湖南 = 999
        //广东  = 8994

        // 对Map按照分组销售总值逆序排序
        Map<String, Long> sortedMap = new LinkedHashMap<>();
        map.entrySet().stream().sorted(Map.Entry.<String, Long> comparingByValue().reversed())
                .forEachOrdered(e -> sortedMap.put(e.getKey(), e.getValue()));

        sortedMap.forEach((k, v) -> System.out.println(k + " = " + v));
        //广东  = 8994
        //四川 = 3995
        //湖北 = 2995
        //湖南 = 999
    }


    /**
     * 使用java8 stream groupingBy操作,按城市分组list并通过join操作连接分组list中的对象的name 属性使用逗号分隔
     */
    public void groupingByString() {
        Map<String, String> map = employees.stream().collect(Collectors.groupingBy(Employee::getCity,
                Collectors.mapping(Employee::getName, Collectors.joining(", ", "Names: [", "]"))));

        map.forEach((k, v) -> System.out.println(k + " = " + v));
        //四川 = Names: [姓名8]
        //湖南 = Names: [姓名1, 姓名2, 姓名7]
        //湖北 = Names: [姓名0, 姓名3, 姓名6, 姓名9]
        //广东  = Names: [姓名4, 姓名5]
    }

    /**
     * 使用java8 stream groupingBy操作,按城市分组list,将List转化为name的List
     */
    public void groupingByList() {
        Map<String, List<String>> map = employees.stream().collect(
                Collectors.groupingBy(Employee::getCity, Collectors.mapping(Employee::getName, Collectors.toList())));

        map.forEach((k, v) -> {
            System.out.println(k + " = " + v);
            v.forEach(item -> System.out.println("item = " + item));
        });
        //四川 = [姓名6]
        //item = 姓名6
        //湖北 = [姓名2, 姓名5, 姓名7]
        //item = 姓名2
        //item = 姓名5
        //item = 姓名7
        //湖南 = [姓名0, 姓名1, 姓名3, 姓名4]
        //item = 姓名0
        //item = 姓名1
        //item = 姓名3
        //item = 姓名4
        //广东  = [姓名8, 姓名9]
        //item = 姓名8
        //item = 姓名9
    }

    /**
     * 使用java8 stream groupingBy操作,按城市分组list,将List转化为name的Set
     */
    public void groupingBySet() {
        Map<String, Set<String>> map = employees.stream().collect(
                Collectors.groupingBy(Employee::getCity, Collectors.mapping(Employee::getName, Collectors.toSet())));

        map.forEach((k, v) -> {
            System.out.println(k + " = " + v);
            v.forEach(item -> System.out.println("item = " + item));
        });
    }

    /**
     * 使用java8 stream groupingBy操作,通过Object对象的成员分组List
     */
    public void groupingByObject() {
        Map<Manage, List<Employee>> map = employees.stream().collect(Collectors.groupingBy(item -> new Manage(item.getName())));

        map.forEach((k, v) -> System.out.println(k + " = " + v));
        //GroupBy.Manage(name=姓名9) = [GroupBy.Employee(city=广东 , name=姓名9, age=19, amount=2998)]
        //GroupBy.Manage(name=姓名8) = [GroupBy.Employee(city=四川, name=姓名8, age=-2, amount=3000)]
        //GroupBy.Manage(name=姓名7) = [GroupBy.Employee(city=广东 , name=姓名7, age=8, amount=2999)]
        //GroupBy.Manage(name=姓名2) = [GroupBy.Employee(city=广东 , name=姓名2, age=10, amount=0)]
        //GroupBy.Manage(name=姓名1) = [GroupBy.Employee(city=四川, name=姓名1, age=-1, amount=1998)]
        //GroupBy.Manage(name=姓名0) = [GroupBy.Employee(city=湖南, name=姓名0, age=17, amount=1997)]
        //GroupBy.Manage(name=姓名6) = [GroupBy.Employee(city=湖北, name=姓名6, age=9, amount=1000)]
        //GroupBy.Manage(name=姓名5) = [GroupBy.Employee(city=湖北, name=姓名5, age=8, amount=1000)]
        //GroupBy.Manage(name=姓名4) = [GroupBy.Employee(city=湖南, name=姓名4, age=-3, amount=3000)]
        //GroupBy.Manage(name=姓名3) = [GroupBy.Employee(city=广东 , name=姓名3, age=17, amount=1999)]
    }

    /**
     * 使用java8 stream groupingBy操作, 基于city 和name 实现多次分组
     */
    public void groupingBys() {
        Map<String, Map<String, List<Employee>>> map = employees.stream()
                .collect(Collectors.groupingBy(Employee::getCity, Collectors.groupingBy(Employee::getName)));

        map.forEach((k, v) -> {
            System.out.println(k + " = " + v);
            v.forEach((i, j) -> System.out.println(i + " = " + j));
        });
        //四川 = {姓名6=[GroupBy.Employee(city=四川, name=姓名6, age=-3, amount=997)]}
        //姓名6 = [GroupBy.Employee(city=四川, name=姓名6, age=-3, amount=997)]

        //湖南 = {姓名5=[GroupBy.Employee(city=湖南, name=姓名5, age=-2, amount=0)]}
        //姓名5 = [GroupBy.Employee(city=湖南, name=姓名5, age=-2, amount=0)]

        //湖北 = {姓名4=[GroupBy.Employee(city=湖北, name=姓名4, age=10, amount=-2)], 姓名3=[GroupBy.Employee(city=湖北, name=姓名3, age=8, amount=997)], 姓名2=[GroupBy.Employee(city=湖北, name=姓名2, age=8, amount=999)], 姓名9=[GroupBy.Employee(city=湖北, name=姓名9, age=29, amount=-2)], 姓名1=[GroupBy.Employee(city=湖北, name=姓名1, age=30, amount=2000)]}
        //姓名4 = [GroupBy.Employee(city=湖北, name=姓名4, age=10, amount=-2)]
        //姓名3 = [GroupBy.Employee(city=湖北, name=姓名3, age=8, amount=997)]
        //姓名2 = [GroupBy.Employee(city=湖北, name=姓名2, age=8, amount=999)]
        //姓名9 = [GroupBy.Employee(city=湖北, name=姓名9, age=29, amount=-2)]
        //姓名1 = [GroupBy.Employee(city=湖北, name=姓名1, age=30, amount=2000)]

        //广东  = {姓名8=[GroupBy.Employee(city=广东 , name=姓名8, age=-1, amount=-3)], 姓名7=[GroupBy.Employee(city=广东 , name=姓名7, age=29, amount=998)], 姓名0=[GroupBy.Employee(city=广东 , name=姓名0, age=0, amount=2999)]}
        //姓名8 = [GroupBy.Employee(city=广东 , name=姓名8, age=-1, amount=-3)]
        //姓名7 = [GroupBy.Employee(city=广东 , name=姓名7, age=29, amount=998)]
        //姓名0 = [GroupBy.Employee(city=广东 , name=姓名0, age=0, amount=2999)]
    }

    /**
     * 使用java8 stream groupingBy操作, 基于Distinct 去重数据
     */
    public void groupingByDistinct() {
        List<Employee> list = employees.stream().filter(distinctByKey(Employee :: getCity))
                .collect(Collectors.toList());

        list.forEach(item-> System.out.println("city = " + item.getCity()));
        //city = 湖南
        //city = 湖北
        //city = 四川
        //city = 广东
    }

    /**
     * 自定义重复key 规则
     */
    private static <T> Predicate<T> distinctByKey(Function<? super T, ?> keyExtractor) {
        Set<Object> seen = ConcurrentHashMap.newKeySet();
        return t -> seen.add(keyExtractor.apply(t));
    }



    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    static
    class Employee {
        private String city;
        private String name;
        private Integer age;
        private Integer amount;

    }

    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    static
    class Manage {
        private String name;

    }

}
